<?php
/**
 * 
 * @package Framework
 * @subpackage Core
 * @author Gustav Calder <gustav.calder@gmail.com>
 */
namespace TwinPeaks\Core;

trait TMagicGetter
{
	
	/**
	 * @var array
	 */
	private $magicGetterContainer = [];
	
	/**
	 * @var array
	 */
	private $magicGetterChanged = [];
	
	/**
	 * @var array
	 */
	private $magicGetterRemoved = [];
	
	/**
	 * @var array
	 */
	private $magicGetterCache = [];
	
	/**
	 * Converts method name in the camel case to field name.
	 * 
	 * @param string $methodName Name of a method being called.
	 * @final
	 * @access public
	 * @return string
	 */
	final static function magicGetterToPropertyName($methodName, &$crumb = null)
	{
		$crumb = substr($methodName, 0, 3);
		$namePart = substr($methodName, 3);
		$namePart[0] = strtolower($namePart[0]);
		return strtolower(preg_replace('~([A-Z])~', '_$1', $namePart));
	}
	
	/**
	 * @final
	 * @static
	 * @access public
	 * @return string
	 */
	final static function magicGetterToCamelCase($string)
	{
		return preg_replace('~_([a-z0-9])~e', 'strtoupper("$1")', $string);
	}
	
	/**
	 * @access protected
	 * @return mixed
	 */
	final protected function magicGetterGetPropertyValue($name)
	{
		return $this->magicGetterContainer[$name];
	}
	
	/**
	 * @param string $name
	 * @param mixed $value
	 * @param boolean $markAsChanged
	 * @return void
	 */
	final protected function magicGetterSetPropertyValue($name, $value, $markAsChanged = false)
	{
		if ($markAsChanged)
			$this->magicGetterChanged[$name] = true;
		$this->magicGetterContainer[$name] = $value;
	}
	
	/**
	 * Returns true if object property is defined.
	 * 
	 * @param string $name Property name.
	 * @final
	 * @access protected
	 * @return boolean
	 */
	final protected function magicGetterHasProperty($name)
	{
		return isset($this->magicGetterContainer[$name]);
	}
	
	/**
	 * @final
	 * @access protected
	 * @return void
	 */
	final protected function magicGetterChangedFlush()
	{
		$this->magicGetterChanged = [];
	}
	
	/**
	 * @return void
	 */
	final protected function magicGetterRemoveProperty($name)
	{
		unset($this->magicGetterContainer[$name]);
		$this->magicGetterChanged[$name] = self::MAGIC_GETTER_FIELD_REMOVED;
	}
	
	/**
	 * @return boolean
	 */
	private function magicGetterIsInCache($fieldName)
	{
		return isset($this->magicGetterCache[$fieldName]);
	}
	
	/**
	 * @param string $fieldName
	 * @return mixed
	 */
	private function magicGetterCacheGet($fieldName)
	{
		return isset($this->magicGetterCache[$fieldName]) ?
			$this->magicGetterCache[$fieldName] : null;
	}
	
	/**
	 * @return void
	 */
	private function magicGetterCacheSet($fieldName, $value)
	{
		$this->magicGetterCache[$fieldName] = $value;
	}
	
	/**
	 * @return void
	 */
	private function magicGetterCacheFlush($fieldName)
	{
		unset($this->magicGetterCache[$fieldName]);
	}
	
	/**
	 * @return mixed
	 */
	final function __call($method, $args)
	{
		$prefix = '';
		$propName = self::magicGetterToPropertyName($method, $prefix);

		if ($prefix == 'get')
		{
			if (method_exists($this, $method))
			{
				// Try to get property value from the cache.
				if ($this->magicGetterIsInCache($propName))
					return $this->magicGetterCacheGet($propName);

				// Cache miss. Method is being called first time or the cache
				// was cleaned up.
				$newValue = call_user_func_array([$this, $method],
					array_merge([$this->magicGetterGetPropertyValue($propName)], $args));
			
				// Cache new property value and return it.
				$this->magicGetterCacheSet($propName, $newValue);
				return $newValue;
			}
			return $this->magicGetterGetPropertyValue($propName);
		}
		
		if ($prefix == 'set')
		{
			$this->magicGetterCacheFlush($propName);
			$value = $args[0];
			return $this->magicGetterSetPropertyValue($propName, $value, true);
		}
		
		if ($prefix == 'has')
		{
			return $this->magicGetterHasProperty($propName);
		}
		
		if ($prefix == 'rmv')
		{
			$this->magicGetterRemoveProperty($propName, true); // Mark as changed.
			return;
		}
	}
}